﻿#region Description
/*  File Name:      MEBaseEntity.cs
 *  Author:         Adam Goodchild
 *  
 *  Description:    This is a base class for all Entities in the Game.
 *                  Almost everything is going to be stored as an Entity.
 *                  A Camera is also stored as an Entity, and so this class
 *                  stores some Cameras specific information.
 *                  
 *  Sources:        Some ideas for this class were taken from the Open Source
 *                  XNA Quick Start Engine. The Camera ideas were adapted from the
 *                  Quick Start Engine.
*/
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using MinimizeEngine.Components;
using MinimizeEngine;
using MinimizeEngine.Managers;
#endregion

namespace MinimizeEngine.Entities
{
    public class MEBaseEntity
    {
        #region Fields

        #region Camera Related Fields
        /// <summary>
        /// Projection Matrix. Used for a Camera Entity
        /// </summary>
        private Matrix mProjectionMatrix = Matrix.Identity;

        /// <summary>
        /// View Matrix. Used for a Camera Entity
        /// </summary>
        private Matrix mViewMatrix = Matrix.Identity;

        /// <summary>
        /// Bounding Frustum. Used for a Camera Entity
        /// </summary>
        private BoundingFrustum mBoundingFrustum;
        #endregion

        /// <summary>
        /// A List of the Components attached to this Entity
        /// </summary>
        private List<MEBaseComponent> mComponentList;

        /// <summary>
        /// A List of all Children Entities attached to this Entity
        /// </summary>
        private List<MEBaseEntity> mChildren;

        /// <summary>
        /// Parent Entity this is attached to
        /// </summary>
        private MEBaseEntity mParent;

        /// <summary>
        /// Position of the Entity in the World
        /// </summary>
        private Vector3 mPosition = Vector3.Zero;

        /// <summary>
        /// The Rotation Matrix for this Entity.
        /// Stores the Right, Up and Forward Vectors.
        /// </summary>
        private Matrix mRotation = Matrix.Identity;

        /// <summary>
        /// The Scale for the Entity
        /// </summary>
        private Vector3 mScale = Vector3.One;

        /// <summary>
        /// The Entities Bounding Sphere
        /// </summary>
        private BoundingSphere mBoundingSphere;

        /// <summary>
        /// The Entities Bounding Box
        /// </summary>
        private BoundingBox mBoundingBox;

        /// <summary>
        /// Bool to determine whether to Draw the Bounding Sphere or not.
        /// </summary>
        private bool mDrawBoundingSphere = false;

        /// <summary>
        /// Bool to determine whether to Draw the Bounding Box or not.
        /// </summary>
        private bool mDrawBoundingBox = false;

        /// <summary>
        /// Name of the Entity
        /// </summary>
        private string mName;

        /// <summary>
        /// Name of the Entity
        /// </summary>
        private string mUniqueName;

        /// <summary>
        /// A Unique ID for this Entity
        /// </summary>
        private Int64 mID = -1;
        private static Int64 mTotalIDs = 0;

        /// <summary>
        /// Is the Entity a Root Entity
        /// </summary>
        private bool mIsRootEntity = false;

        #endregion

        #region Properties

        #region Camera Related Properties

        /// <summary>
        /// Get the Projection Matrix for this Entity
        /// Only used for a Camera Entity
        /// </summary>
        public Matrix Projection
        {
            get { return mProjectionMatrix; }
            set { mProjectionMatrix = value; }
        }

        /// <summary>
        /// Get the View Matrix for this Entity
        /// Only used for a Camera Entity
        /// </summary>
        public Matrix View
        {
            get { return mViewMatrix; }
            set { mViewMatrix = value; }
        }

        /// <summary>
        /// The Bounding Frustum for this Entity.
        /// Only used for a Camera Entity
        /// </summary>
        public BoundingFrustum BoundingFrustum
        {
            get { return mBoundingFrustum; }
            set { mBoundingFrustum = value; }
        }

        /// <summary>
        /// Point the Camera to a certain Position
        /// </summary>
        /// <param name="target">The posiiton to point the Camera at</param>
        public void LookAt(Vector3 target)
        {
            //Forward Vector
            Vector3 forward = target - mPosition;
            forward.Normalize();

            //Matrix Rotation
            Matrix rotation = Matrix.Identity;
            rotation.Forward = forward;

            //Reference
            Vector3 reference = Vector3.UnitY;

            //We have to check here that the Camera is not perfectly parallel
            //with the Y-Axis. We cannot use Cross Product if it is, so we shall swap
            //the Reference to the Forward Vector.
            if(rotation.Forward.Y == reference.Y || rotation.Forward.Y == -reference.Y)
            {
                reference = Vector3.UnitZ;
            }

            //Set the Vectors properly
            rotation.Right = Vector3.Cross(rotation.Forward, reference);
            rotation.Up = Vector3.Cross(rotation.Right, rotation.Forward);
        }

        /// <summary>
        /// Yaw the Camera about the Worlds Up Vector
        /// </summary>
        /// <param name="yaw">Amount to Yaw about</param>
        public void Yaw(float yaw)
        {
            mRotation *= Matrix.CreateRotationY(yaw);
        }

        /// <summary>
        /// Pitch the Camera about its Right Vector
        /// </summary>
        /// <param name="pitch">Amount to Pitch about</param>
        public void Pitch(float pitch)
        {
            mRotation *= Matrix.CreateFromAxisAngle(mRotation.Right, pitch);
        }

        /// <summary>
        /// Roll the Camera about its Forward Vector
        /// </summary>
        /// <param name="pitch">Amount to Roll about</param>
        public void Roll(float roll)
        {
            mRotation *= Matrix.CreateFromAxisAngle(mRotation.Forward, roll);
        }

        #endregion

        /// <summary>
        /// Get a List of all Interfaces attached to this Entity
        /// </summary>
        public List<MEBaseComponent> ComponentList
        {
            get{ return mComponentList; }
        }

        /// <summary>
        /// Get a List of all Children Entities attached to this Entity
        /// </summary>
        public List<MEBaseEntity> Children
        {
            get { return mChildren; }
        }

        /// <summary>
        /// Get the Parent Entity of this Entity
        /// </summary>
        public MEBaseEntity Parent
        {
            get{ return mParent; }
        }

        /// <summary>
        /// The Position of the Entity
        /// </summary>
        public Vector3 Position
        {
            get { return mPosition; }
            set { mPosition = value; }
        }

        /// <summary>
        /// The Rotation Matrix of the Entity
        /// </summary>
        public Matrix Rotation
        {
            get { return mRotation; }
            set { mRotation = value; }
        }

        /// <summary>
        /// The Scale of the Entity
        /// </summary>
        public Vector3 Scale
        {
            get { return mScale; }
        }

        /// <summary>
        /// Get or Set the Entities Bounding Sphere
        /// </summary>
        public BoundingSphere BoundingSphere
        {
            get { return mBoundingSphere; }
            set { mBoundingSphere = value; }
        }

        /// <summary>
        /// Get or Set the Entities Bounding Box
        /// </summary>
        public BoundingBox BoundingBox
        {
            get { return mBoundingBox; }
            set { mBoundingBox = value; }
        }

        /// <summary>
        /// Draw the Bounding Sphere or not?
        /// </summary>
        public bool DrawBoundingSphere
        {
            get { return mDrawBoundingSphere; }
            set { mDrawBoundingSphere = value; }
        }

        /// <summary>
        /// Is the Entity a Root Entity
        /// </summary>
        public bool IsRootEntity
        {
            get { return mIsRootEntity; }
            set { mIsRootEntity = value; }
        }

        /// <summary>
        /// Draw the Bounding Box or not?
        /// </summary>
        public bool DrawBoundingBox
        {
            get { return mDrawBoundingBox; }
            set { mDrawBoundingBox = value; }
        }

        /// <summary>
        /// The Name of the Entity
        /// </summary>
        public string Name
        {
            get { return mName; }
        }

        /// <summary>
        /// The Unique Name of the Entity. Uses the entities ID to give it a Unique Name.
        /// </summary>
        public string UniqueName
        {
            get { return mUniqueName; }
        }

        /// <summary>
        /// The Entities ID
        /// </summary>
        public Int64 ID
        {
            get { return mID; }
        }
        
        /// <summary>
        /// Strafe the Entity along its Right Vector
        /// </summary>
        /// <param name="strafe">Amount to Strafe by</param>
        public void Strafe(float strafe)
        {
            mPosition += mRotation.Right * strafe;
        }

        /// <summary>
        /// Move the Entity along its Forward vector
        /// </summary>
        /// <param name="move">Amount to Move by</param>
        public void Move(float move)
        {
            mPosition += mRotation.Forward * move;
        }

        /// <summary>
        /// Add a new Component to the Entity
        /// </summary>
        /// <param name="baseComponent"></param>
        public void AddComponent(MEBaseComponent baseComponent)
        {
            mComponentList.Add(baseComponent);
        }

        /// <summary>
        /// Add a new Child Entity to this Entity
        /// </summary>
        /// <param name="child"></param>
        public void AddChildEntity(MEBaseEntity child)
        {
            mChildren.Add(child);
            child.SetParent(this);
        }

        /// <summary>
        /// Set the Parent for this Entity
        /// </summary>
        /// <param name="parent"></param>
        public void SetParent(MEBaseEntity parent)
        {
            if(parent != null)
            {
                mParent = parent;
            }
        }

        /// <summary>
        /// Check to see if the Entity has a Parent Entity
        /// </summary>
        public bool HasGotParent
        {
            get { return (mParent != null); }
        }

        /// <summary>
        /// Check to see if the Entity has a Parent Entity
        /// </summary>
        public bool HasGotChildren
        {
            get { return (mChildren.Count > 0); }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Construct a new Base Entity.
        /// Note: Its name will consist of your input name, plus its Unique ID.
        /// </summary>
        /// <param name="name">Name of Entity. Its Unique ID will be added.</param>
        /// <param name="position">Position of the Entity</param>
        /// <param name="rotation">Rotation of the Entity</param>
        public MEBaseEntity(string name, Vector3 position, Matrix rotation)
        {
            //Setup the Lists
            mComponentList = new List<MEBaseComponent>();
            mChildren = new List<MEBaseEntity>();
            
            //Set the Unique ID
            mID = mTotalIDs;
            mName = name;
            mUniqueName = mName + "-" + mID;
            mPosition = position;
            mRotation = rotation;

            //Now increase the Number of ID's used so all Entities
            //have a unique ID
            mTotalIDs++;

            MESceneGraphManager.AddEntity(this);
        }

        /// <summary>
        /// Construct a new Base Entity.
        /// Note: Its name will consist of your input name, plus its Unique ID.
        /// </summary>
        /// <param name="name">Name of Entity. Its Unique ID will be added.</param>
        /// <param name="position">Position of the Entity</param>
        /// <param name="rotation">Rotation of the Entity</param>
        /// <param name="isRoot">Is the Entity a Root Entity</param>
        public MEBaseEntity(string name, Vector3 position, Matrix rotation, bool isRoot)
        {
            if(isRoot)
            {
                //Is Root?
                IsRootEntity = isRoot;

                //Setup the Lists
                mComponentList = new List<MEBaseComponent>();
                mChildren = new List<MEBaseEntity>();

                //Set the Unique ID
                mID = mTotalIDs;
                mName = name;
                mUniqueName = mName + "-" + mID;
                mPosition = position;
                mRotation = rotation;

                //Now increase the Number of ID's used so all Entities
                //have a unique ID
                mTotalIDs++;

                MESceneGraphManager.AddEntity(this);
            }            
        }

        #endregion

        #region Update

        /// <summary>
        /// Update the Entity.
        /// Also Updates its Children and Interfaces.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            foreach (MEBaseComponent baseComponent in mComponentList)
            {
                baseComponent.Update(gameTime);
            }

            foreach (MEBaseEntity child in mChildren)
            {
                child.Update(gameTime);
            }
        }

        #endregion

        #region Draw

        /// <summary>
        /// Draw the Entity.
        /// Also Draws all Children Entities.
        /// </summary>
        public void Draw()
        {
            foreach (MEBaseComponent baseComponent in mComponentList)
            {
                baseComponent.Draw();
            }
        }

        #endregion
    }
}
